﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using WindowService;

namespace ConsoleProcessMonitor
{
    public class VersionHelper
    {
        public static string SavePath = Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\CDSoft\CProcessMonitor";
        public Ini ini = null;

        public VersionHelper(Ini ini)
        {
            this.ini = ini;
        }

        public void upgrade(JObject versionJson)
        {
            Log.WriteLog("VersionHelper Start check upgrade version ", LogType.Trace);
            int timeOut = 0;  //进程升级超时时间
            int upgradeSleepTime = getUpgradeDoneSleepTime() * 1000;  //升级完成后sleep时间
            int oneUpgradeDoneSleepTime = getOneUpgradeDoneSleepTime() * 1000; //单个任务升级完成后sleep时间
            int checkProcessInterval = 2000;         //检查进程存活周期 2s
            Dictionary<string, string[]> upgradeList = getUpgradeList(versionJson);
            Log.WriteLog("VersionHelper Get upgrade list \"" + parseObject(upgradeList) + "\"", LogType.Trace);
            //不需要升级
            if (upgradeList == null || upgradeList.Count == 0)
            {
                Log.WriteLog("Upgrade cancel, server version is same as local version", LogType.Error);
                return;
            }

            Dictionary<string, string> runUpgradeList = new Dictionary<string, string>();
            foreach(var upgradeItem in upgradeList)
            {
                //开始下载升级列表
                string savePath = downloadFile(upgradeItem.Value[1], upgradeItem.Value[2]);
                if(savePath != null) runUpgradeList.Add(upgradeItem.Key, savePath);
            }
            Log.WriteLog("VersionHelper Get need run upgrade list \"" + parseObject(runUpgradeList) + "\"", LogType.Trace);
            //发送系统升级消息
            if (runUpgradeList.Count > 0)
            {
                Log.WriteLog("VersionHelper Send Broadcast System message \"" + MessageType.UPGRADE_MESSAGE.ToString() + "\"", LogType.Trace);
                SystemHelper.broadcastMessage(MessageType.UPGRADE_MESSAGE.ToString());
            }
            foreach (var runUpgradeItem in runUpgradeList)
            {
                timeOut = getOneUpgradeMaxTimeOut() * 1000;  //进程升级超时时间
                try
                {
                    Process process = getNowRunningProcess(runUpgradeItem.Key);
                    if (process == null)
                    {
                        Log.WriteLog("VersionHelper Running upgrade process \"" + runUpgradeItem.Value + "\"", LogType.Trace);
                        Process.Start(runUpgradeItem.Value);
                    }

                    //等待, 然后检测是否已经运行
                    Thread.Sleep(oneUpgradeDoneSleepTime);
                    //没2s检查该进程是否存活
                    //如果未存活，则继续下一个进程
                    //如果在进程列表中，则检查是否超时，若超时，则 kill 该进程
                    while (getNowRunningProcess(runUpgradeItem.Key) != null)
                    {
                        process = getNowRunningProcess(runUpgradeItem.Key);
                        timeOut -= checkProcessInterval;
                        if (timeOut <= 0)
                        {
                            process.Kill();
                            Log.WriteLog("VersionHelper Upgrade Timeout, kill the process \"" + runUpgradeItem.Key + "\"", LogType.Error);
                        }
                        Thread.Sleep(checkProcessInterval);
                    }
                }
                catch (Exception e)
                {
                    Log.WriteLog("Run upgrade file \"" + runUpgradeItem.Value + "\" error:" + e.Message, LogType.Error);
                }
            }
            if (runUpgradeList.Count > 0)
            {
                Version newVersion = parseJson(versionJson);
                saveVersionInfo(newVersion);
                deleteDownloadDirectory();

                Log.WriteLog("VersionHelper Upgrade Done, need to Reset System at " + upgradeSleepTime + "s later", LogType.Error);
                //睡眠
                Thread.Sleep(upgradeSleepTime);
                //重启
                SystemHelper.Restart(10);
            }
        }

        //获取进程升级的最大超时时间 s
        public int getOneUpgradeMaxTimeOut()
        {
            return ini.getIntConfig("Setting", "OneUpgradeMaxTimeOut", 60, 65535, 60);
        }
        //获取每个进程升级完成后的等待时间 s
        public int getOneUpgradeDoneSleepTime()
        {
            return ini.getIntConfig("Setting", "OneUpgradeDoneSleepTime", 0, 65535, 0);
        }
        //获取进程升级完成后的等待时间 s
        public int getUpgradeDoneSleepTime()
        {
            return ini.getIntConfig("Setting", "UpgradeDoneSleepTime", 10, 65535, 10);
        }
        public Process getNowRunningProcess(string processName)
        {
            Process[] processes = Process.GetProcesses();
            for (int i = 0; i < processes.Length; i++)
            {
                if (processName == processes[i].ProcessName) return processes[i];
            }
            return null;
        }
        public void deleteDownloadDirectory()
        {
            string saveFilePath = SavePath + "\\upgradeFiles\\";

            Log.WriteLog("VersionHelper Delete download directory \"" + saveFilePath + "\" ", LogType.Trace);
            try
            {
                if (Directory.Exists(saveFilePath)) Directory.Delete(saveFilePath, true);
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Delete download directory \"" + saveFilePath + "\" error:" + e.ToString(), LogType.Error);
            }
        }
        public string downloadFile(string downloadUrl, string md5)
        {
            string saveFilePath = SavePath + "upgradeFiles\\";
            string baseUrl = "";
            NameValueCollection nvc = new NameValueCollection();
            string saveFileName = "";
            try
            {
                //解析保存文件
                parseUrl(downloadUrl, out baseUrl, out nvc);

                saveFileName = nvc["fileName"];
                if (saveFileName == null)
                {
                    Log.WriteLog("VersionHelper parse Download url failed, url not contain string\"fileName\" ", LogType.Error);
                    return null;
                }

                saveFileName = saveFilePath + saveFileName;
                Log.WriteLog("VersionHelper start Download file: \"" + downloadUrl + "\" ", LogType.Trace);

                if (!Directory.Exists(saveFilePath))
                {
                    Directory.CreateDirectory(saveFilePath);
                    Log.WriteLog("VersionHelper it is not exist file path: \"" + saveFilePath + "\", created ", LogType.Trace);
                }
                if (File.Exists(saveFileName))
                {
                    File.Delete(saveFileName);
                    Log.WriteLog("VersionHelper it is exist old file: \"" + saveFileName + "\", deleted ", LogType.Trace);
                }

                WebClient client = new WebClient();
                Log.WriteLog("VersionHelper Ready download file: \"" + downloadUrl + "\" to \"" + saveFileName + "\" ", LogType.Trace);
                client.DownloadFile(downloadUrl, saveFileName);
                Log.WriteLog("VersionHelper Download file: \"" + downloadUrl + "\" to \"" + saveFileName + "\" success ", LogType.Trace);

                Log.WriteLog("VersionHelper Download file: \"" + downloadUrl + "\" to \"" + saveFileName + "\" success ", LogType.Trace);
                string computeMd5 = getMD5Hash(saveFileName);
                if (computeMd5.ToLower() != md5.ToLower())
                {
                    File.Delete(saveFileName);
                    Log.WriteLog("VersionHelper Verify file \"" + saveFileName + "\" failed, Compute value(\"" + computeMd5.ToLower() + "\") is not eq Compare value:(\"" + md5.ToLower() + "\") deleted ", LogType.Error);
                    return null;
                }
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Download file \"" + downloadUrl + "\" error: " + e.Message, LogType.Error);
                return null;
            } 
            
            Log.WriteLog("VersionHelper Verify file \"" + saveFileName + "\" success ", LogType.Trace);
                    
            return saveFileName;
        }
        //计算文件的MD5码
        private string getMD5Hash(string pathName)
        {
            string strResult = "";
            string strHashData = "";
 
            byte[] arrbytHashValue;
            System.IO.FileStream oFileStream = null;
 
            System.Security.Cryptography.MD5CryptoServiceProvider oMD5Hasher =
                       new System.Security.Cryptography.MD5CryptoServiceProvider();
 
            try
            {
                oFileStream = new System.IO.FileStream(pathName, System.IO.FileMode.Open,
                      System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite);
                arrbytHashValue = oMD5Hasher.ComputeHash(oFileStream);//计算指定Stream 对象的哈希值
                oFileStream.Close();
                //由以连字符分隔的十六进制对构成的String，其中每一对表示value 中对应的元素；例如“F-2C-4A”
                strHashData = System.BitConverter.ToString(arrbytHashValue);
                //替换-
                strHashData = strHashData.Replace("-", "");
                strResult = strHashData;
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Compute md5 \"" + pathName + "\" error: " + e.Message, LogType.Error);
            }
 
            return strResult;
        }
        public Dictionary<string, string[]> getUpgradeList(JObject versionJson) {
            Dictionary<string, string[]> upgradeList = new Dictionary<string, string[]>();

            Version newVersion = parseJson(versionJson);
            Version oldVersion = readVersionInfo();

            Log.WriteLog("VersionHelper Compare version and get upgrade list old version:\"" + parseObject(oldVersion)
                + "\", new version:\"" + parseObject(newVersion) + "\"", LogType.Trace);

            if (newVersion == null)
            {
                Log.WriteLog("VersionHelper new version is NULL, return empty upgrade list", LogType.Trace);
                return upgradeList;
            }
            if (oldVersion == null)
            {
                foreach (var newUpgradeItem in newVersion.upgradeList)
                {
                    string upgradeItemName = getUpgradeItemName(newUpgradeItem.Value[1]);
                    if(!upgradeList.ContainsKey(upgradeItemName)) upgradeList.Add(upgradeItemName, newUpgradeItem.Value);
                }
                Log.WriteLog("VersionHelper old version is NULL, return all new upgrade list:\"" + parseObject(upgradeList) + "\"", LogType.Trace);
                return upgradeList;
            }
            if (newVersion.version == oldVersion.version)
            {
                Log.WriteLog("VersionHelper old version(" + oldVersion.version + ") is eq new version(" + newVersion.version + "), Not need upgrade, return empty upgrade list", LogType.Trace);
                return upgradeList;
            }
            foreach (var newUpgradeItem in newVersion.upgradeList)
            {
                string newId = newUpgradeItem.Key;
                string newVersionNum = newUpgradeItem.Value[0];
                if (!oldVersion.upgradeList.ContainsKey(newId))
                {
                    string upgradeItemName = getUpgradeItemName(newUpgradeItem.Value[1]);
                    if (!upgradeList.ContainsKey(upgradeItemName))
                    {
                        Log.WriteLog("VersionHelper old version not contain new version's key:" + newId + " add \"" + upgradeItemName + "\" upgrade list", LogType.Trace);
                        upgradeList.Add(upgradeItemName, newUpgradeItem.Value);
                    }
                    continue;
                }
                foreach (var oldUpgradeItem in oldVersion.upgradeList)
                {
                    string oldId = oldUpgradeItem.Key;
                    string oldVersionNum = oldUpgradeItem.Value[0];
                    if (newId == oldId){
                        if (newVersionNum != oldVersionNum)
                        {
                            string upgradeItemName = getUpgradeItemName(newUpgradeItem.Value[1]);
                            if (!upgradeList.ContainsKey(upgradeItemName))
                            {
                                Log.WriteLog("VersionHelper old version's key(" + oldId + ") is eq new version's key(" + newId
                                    + ") and old version's number(" + oldVersionNum + ") is not eq new version's number(" + newVersionNum + "), add \""
                                    + upgradeItemName + "\" upgrade list", LogType.Trace);
                                upgradeList.Add(upgradeItemName, newUpgradeItem.Value);
                            }
                        }
                        break;
                    }
                }
            }
            return upgradeList;
        }
        //根据升级路径获取升级进程名称
        public string getUpgradeItemName(string url)
        {
            string[] spaceItems = url.Split(' ');
            string spaceItem = "";
            string upgradeItem = "";
            string upgradeItemName = "";
            try
            {
                //截取非启动参数url地址
                if (spaceItems.Length > 0)
                {
                    spaceItem = spaceItems[0];
                }
                //截取最后一个文件的名称
                upgradeItem = System.IO.Path.GetFileName(spaceItem);
                //截取后缀前的文件名称
                int upgradeItemPointPosition = upgradeItem.LastIndexOf('.');
                if (upgradeItemPointPosition > 0)
                {
                    upgradeItemName = upgradeItem.Substring(0, upgradeItemPointPosition);
                }
                else if (upgradeItemPointPosition == -1)
                {
                    upgradeItemName = upgradeItem;
                }
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Parse upgrade path \"" + url + "\" error: " + e.Message, LogType.Error);
                upgradeItemName = "";
            }
            Log.WriteLog("VersionHelper Parse upgrade path \"" + url + "\" return process name \"" + upgradeItemName + "\"", LogType.Trace);

            return upgradeItemName;
        }
        public Version parseJson(JObject versionJson)
        {
            Version version = new Version();
            try{
                version.version = versionJson["version"].ToString();
                JArray upgradeList = (JArray)versionJson["upgradeList"];
                version.upgradeList = new Dictionary<string, string[]>();

                for (int i = 0; i < upgradeList.Count; i++)
                {
                    string[] upgradeItem = new string[3];
                    upgradeItem[0] = upgradeList[i]["version"].ToString();
                    upgradeItem[1] = upgradeList[i]["upgradeUrl"].ToString();
                    upgradeItem[2] = upgradeList[i]["md5"].ToString();
                    version.upgradeList.Add(upgradeList[i]["id"].ToString(), upgradeItem);
                }
            }
            catch(Exception e)
            {
                Log.WriteLog("VersionHelper Parse version file error: " + e.Message, LogType.Error);
                return null;
            }
            return version;
        }
        public void saveVersionInfo(Version version)
        {
            string versionFilePath = SavePath + "upgrade.data";
            Log.WriteLog("VersionHelper Ready to Save local version file: " + versionFilePath, LogType.Trace);

            try
            {
                if (!Directory.Exists(SavePath))
                {
                    Log.WriteLog("VersionHelper local version directory \"" + SavePath + "\" is not exist, created", LogType.Trace);
                    Directory.CreateDirectory(SavePath);
                }
                if (File.Exists(versionFilePath))
                {
                    Log.WriteLog("VersionHelper local version file \"" + versionFilePath + "\" was exist, deleted ", LogType.Trace);
                    File.Delete(versionFilePath);
                }

                Stream fStream = new FileStream(versionFilePath, FileMode.Create, FileAccess.ReadWrite);
                BinaryFormatter binFormat = new BinaryFormatter();//创建二进制序列化器
                binFormat.Serialize(fStream, version);
                fStream.Close();

                Log.WriteLog("VersionHelper Save Local Version file success, content: " + parseObject(version), LogType.Trace);
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Create version file error: " + e.Message, LogType.Error);
            }
        }

        public Version readVersionInfo()
        {
            string versionFilePath = SavePath + "upgrade.data";
            Log.WriteLog("VersionHelper Ready to read local version file: " + versionFilePath, LogType.Trace);
            Version version = null;
            try
            {
                if (!Directory.Exists(SavePath))
                {
                    Log.WriteLog("VersionHelper local version directory \"" + SavePath + "\" is not exist, return local version: NULL", LogType.Trace);
                    return version;
                }
                if (!File.Exists(versionFilePath))
                {
                    Log.WriteLog("VersionHelper local version file \"" + versionFilePath + "\" is not exist, return local version: NULL", LogType.Trace);
                    return version;
                }

                Stream fStream = new FileStream(versionFilePath, FileMode.Open, FileAccess.ReadWrite);
                BinaryFormatter binFormat = new BinaryFormatter();//创建二进制序列化器
                version = (Version)binFormat.Deserialize(fStream);
                fStream.Close();
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Create version file error: " + e.Message, LogType.Error);
            }
            Log.WriteLog("VersionHelper Read Local Version file success, content: " + parseObject(version).ToString(), LogType.Trace);
            return version;
        }

        private string parseObject(object obj)
        {
            try
            {
                return (obj == null) ? "NULL" : Newtonsoft.Json.Linq.JObject.FromObject(obj).ToString();
            }
            catch (Exception e)
            {
                Log.WriteLog("VersionHelper Parse object error: " + e.Message, LogType.Error);
                return "Parse Exception";
            }
        }
        /// <summary>
        /// 分析 url 字符串中的参数信息
        /// </summary>
        /// <param name="url">输入的 URL</param>
        /// <param name="baseUrl">输出 URL 的基础部分</param>
        /// <param name="nvc">输出分析后得到的 (参数名,参数值) 的集合</param>
        static void parseUrl(string url, out string baseUrl, out NameValueCollection nvc)
        {
            if (url == null)
                throw new ArgumentNullException("url");

            nvc = new NameValueCollection();
            baseUrl = "";

            if (url == "")
                return;

            int questionMarkIndex = url.IndexOf('?');

            if (questionMarkIndex == -1)
            {
                baseUrl = url;
                return;
            }
            baseUrl = url.Substring(0, questionMarkIndex);
            if (questionMarkIndex == url.Length - 1)
                return;
            string ps = url.Substring(questionMarkIndex + 1);

            // 开始分析参数对    
            Regex re = new Regex(@"(^|&)?(\w+)=([^&]+)(&|$)?", RegexOptions.Compiled);
            MatchCollection mc = re.Matches(ps);

            foreach (Match m in mc)
            {
                nvc.Add(m.Result("$2"), m.Result("$3"));
            }
        }

        public enum MessageType
        {
            UPGRADE_MESSAGE = 0
        }
    }

    [Serializable]
    public class Version
    {
        public string version { get; set; }
        public Dictionary<string, string[]> upgradeList { get; set; }
    }
}
